Passed
Branch master (bf3828)
by Rafael S.
02:20
created

index.js ➔ ???   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 211

Duplication

Lines 0
Ratio 0 %

Importance

Changes 26
Bugs 1 Features 0
Metric Value
c 26
b 1
f 0
nc 2
dl 0
loc 211
rs 8.2857
cc 2
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/*!
2
 * wavefile
3
 * Read & write wave files with 4, 8, 11, 12, 16, 20, 24, 32 & 64-bit data.
4
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
5
 * https://github.com/rochars/wavefile
6
 *
7
 */
8
9
/** @private */
10
const bitDepth_ = require("bitdepth");
11
/** @private */
12
const riffChunks_ = require("riff-chunks");
13
/** @private */
14
const imaadpcm_ = require("imaadpcm");
15
/** @private */
16
const alawmulaw_ = require("alawmulaw");
17
/** @private */
18
const byteData_ = require("byte-data");
19
/** @private */
20
let uInt8_ = require("byte-data").uInt8;
21
/** @private */
22
let uInt16_ = require("byte-data").uInt16;
23
/** @private */
24
let uInt32_ = require("byte-data").uInt32;
25
26
/**
27
 * Class representing a wav file.
28
 */
29
class WaveFile {
30
31
    /**
32
     * @param {!Uint8Array|!Array<number>} bytes A wave file buffer.
33
     * @throws {Error} If no "RIFF" chunk is found.
34
     * @throws {Error} If no "fmt " chunk is found.
35
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
36
     * @throws {Error} If no "data" chunk is found.
37
     */
38
    constructor(bytes) {
39
        /**
40
         * The container identifier.
41
         * Only "RIFF" and "RIFX" are supported.
42
         * @type {!string}
43
         */
44
        this.container = "";
45
        /** @type {!number} */
46
        this.chunkSize = 0;
47
        /**
48
         * The format.
49
         * Always "WAVE".
50
         * @type {!string}
51
         */
52
        this.format = "";
53
54
        /**
55
         * The data of the "fmt" chunk.
56
         * @type {!Object<string, *>}
57
         * @nocollapse
58
         */
59
        this.fmt = {
60
            /** @type {!string} */
61
            "chunkId": "",
62
            /** @type {!number} */
63
            "chunkSize": 0,
64
            /** @type {!number} */
65
            "audioFormat": 0,
66
            /** @type {!number} */
67
            "numChannels": 0,
68
            /** @type {!number} */
69
            "sampleRate": 0,
70
            /** @type {!number} */
71
            "byteRate": 0,
72
            /** @type {!number} */
73
            "blockAlign": 0,
74
            /** @type {!number} */
75
            "bitsPerSample": 0,
76
            /** @type {!number} */
77
            "cbSize": 0,
78
            /** @type {!number} */
79
            "validBitsPerSample": 0,
80
            /** @type {!number} */
81
            "dwChannelMask": 0,
82
            /**
83
             * 4 32-bit values representing a 128-bit ID
84
             * @type {!Array<number>}
85
             */
86
            "subformat": []
87
        };
88
89
        /**
90
         * The data of the "fact" chunk.
91
         * @type {!Object<string, *>}
92
         * @nocollapse
93
         */
94
        this.fact = {
95
            /** @type {!string} */
96
            "chunkId": "",
97
            /** @type {!number} */
98
            "chunkSize": 0,
99
            /** @type {!number} */
100
            "dwSampleLength": 0
101
        };
102
        
103
        /**
104
         * The data of the "cue " chunk.
105
         * @type {!Object<string, *>}
106
         * @nocollapse
107
         */
108
        this.cue = {
109
            /** @type {!string} */
110
            "chunkId": "",
111
            /** @type {!number} */
112
            "chunkSize": 0,
113
            /** @type {!number} */
114
            "dwCuePoints": 0,
115
            /** @type {!Object<string, string|number>} */
116
            "points": [],
117
        };
118
119
        /**
120
         * The data of the "bext" chunk.
121
         * @type {!Object<string, *>}
122
         * @nocollapse
123
         */
124
        this.bext = {
125
            /** @type {!string} */
126
            "chunkId": "",
127
            /** @type {!number} */
128
            "chunkSize": 0,
129
            /** @type {!string} */
130
            "description": "", //256
131
            /** @type {!string} */
132
            "originator": "", //32
133
            /** @type {!string} */
134
            "originatorReference": "", //32
135
            /** @type {!string} */
136
            "originationDate": "", //10
137
            /** @type {!string} */
138
            "originationTime": "", //8
139
            /**
140
             * 2 32-bit values, timeReference high and low
141
             * @type {!Array<number>}
142
             */
143
            "timeReference": [],
144
            /** @type {number} */
145
            "version": 0, //WORD
146
            /** @type {!string} */
147
            "UMID": "", // 64 chars
148
            /** @type {!number} */
149
            "loudnessValue": 0, //WORD
150
            /** @type {!number} */
151
            "loudnessRange": 0, //WORD
152
            /** @type {!number} */
153
            "maxTruePeakLevel": 0, //WORD
154
            /** @type {!number} */
155
            "maxMomentaryLoudness": 0, //WORD
156
            /** @type {!number} */
157
            "maxShortTermLoudness": 0, //WORD
158
            /** @type {!string} */
159
            "reserved": "", //180
160
            /** @type {!string} */
161
            "codingHistory": "" // string, unlimited
162
        };
163
164
        /**
165
         * The data of the "ds64" chunk.
166
         * Used only with RF64 files.
167
         * @type {!Object<string, *>}
168
         * @nocollapse
169
         */
170
        this.ds64 = {
171
            /** @type {!string} */
172
            "chunkId": "",
173
            /** @type {!number} */
174
            "riffSizeHigh": 0, // DWORD
175
            /** @type {!number} */
176
            "riffSizeLow": 0, // DWORD
177
            /** @type {!number} */
178
            "dataSizeHigh": 0, // DWORD
179
            /** @type {!number} */
180
            "dataSizeLow": 0, // DWORD
181
            /** @type {!number} */
182
            "originationTime": 0, // DWORD
183
            /** @type {!number} */
184
            "sampleCountHigh": 0, // DWORD
185
            /** @type {!number} */
186
            "sampleCountLow": 0, // DWORD
187
            /** @type {!number} */
188
            "tableLength": 0, // DWORD
189
            /** @type {!Array} */
190
            "table": []
191
        };
192
193
        /**
194
         * The data of the "data" chunk.
195
         * @type {!Object<string, *>}
196
         * @nocollapse
197
         */
198
        this.data = {
199
            /** @type {!string} */
200
            "chunkId": "",
201
            /** @type {!number} */
202
            "chunkSize": 0,
203
            /** @type {!Array<number>} */
204
            "samples": []
205
        };
206
207
        /**
208
         * @type {!string}
209
         */
210
        this.bitDepth = "0";
211
212
        /**
213
         * Audio formats.
214
         * Formats not listed here will be set to 65534
215
         * and treated as WAVE_FORMAT_EXTENSIBLE
216
         * @enum {!number}
217
         * @const
218
         */
219
        this.audioFormats_ = {
220
            "4": 17,
221
            "8": 1,
222
            "8a": 6,
223
            "8m": 7,
224
            "16": 1,
225
            "24": 1,
226
            "32": 1,
227
            "32f": 3,
228
            "40": 65534,
229
            "48": 65534,
230
            "64": 3
231
        };
232
        /**
233
         * @type {!number}
234
         * @private
235
         */
236
        this.head_ = 0;
237
        /**
238
         * If the "fact" chunk should be enforced or not.
239
         * @type {!boolean}
240
         * @private
241
         */
242
        this.enforceFact_ = false;
243
        /**
244
         * If the data in data.samples is interleaved or not.
245
         * @type {!boolean}
246
         */
247
        this.isInterleaved = true;
248
249
        if(bytes) {
250
            this.fromBuffer(bytes);
251
        }
252
    }
253
254
    /**
255
     * Set up a WaveFile object based on the arguments passed.
256
     * @param {!number} numChannels The number of channels
257
     *     (Integer numbers: 1 for mono, 2 stereo and so on).
258
     * @param {!number} sampleRate The sample rate.
259
     *     Integer numbers like 8000, 44100, 48000, 96000, 192000.
260
     * @param {!string} bitDepth The audio bit depth.
261
     *     One of "4", "8", "8a", "8m", "16", "24", "32", "32f", "64"
262
     *     or any value between "8" and "32".
263
     * @param {!Array<number>} samples Array of samples to be written.
264
     *     The samples must be in the correct range according to the
265
     *     bit depth.
266
     * @throws {Error} If any argument does not meet the criteria.
267
     */
268
    fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
269
        if (!options["container"]) {
270
            options["container"] = "RIFF";
271
        }
272
        
273
        // closest nuber of bytes if not / 8
274
        let numBytes = (((parseInt(bitDepth, 10) - 1) | 7) + 1) / 8;
275
        this.clearHeader_();
276
        this.bitDepth = bitDepth;
277
278
        // Normal PCM file header
279
        this.container = options["container"];
280
        this.chunkSize = 36 + samples.length * numBytes;
281
        this.format = "WAVE";
282
        this.fmt.chunkId = "fmt ";
283
        this.fmt.chunkSize = 16;
284
        this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
285
        this.fmt.blockAlign = numChannels * numBytes;
286
        this.fmt.audioFormat = this.audioFormats_[bitDepth] ?
287
            this.audioFormats_[bitDepth] : 65534;
288
        this.fmt.numChannels = numChannels;
289
        this.fmt.sampleRate = sampleRate;
290
        this.fmt.bitsPerSample = parseInt(bitDepth, 10);
291
        this.data.chunkId = "data";
292
        this.data.samples = samples;
293
        // interleave the samples if they were passed de-interleaved
294
        if (samples.length > 0) {
295
            if (samples[0].constructor === Array) {
296
                this.isInterleaved = false;
297
                this.interleave();
298
            }
299
        }
300
        this.data.chunkSize = samples.length * numBytes;
301
302
        // IMA ADPCM header
303
        if (bitDepth == "4") {
304
            this.chunkSize = 44 + samples.length;
305
            this.fmt.chunkSize = 20;
306
            this.fmt.byteRate = 4055;
307
            this.fmt.blockAlign = 256;
308
            this.fmt.bitsPerSample = 4;
309
            this.fmt.cbSize = 2;
310
            this.fmt.validBitsPerSample = 505;
311
            this.fact.chunkId = "fact";
312
            this.fact.chunkSize = 4;
313
            this.fact.dwSampleLength = samples.length * 2;
314
            this.data.chunkSize = samples.length;
315
        }
316
        // A-Law and mu-Law header
317
        if (bitDepth == "8a" || bitDepth == "8m") {
318
            this.chunkSize = 44 + samples.length;
319
            this.fmt.chunkSize = 20;
320
            this.fmt.cbSize = 2;
321
            this.fmt.validBitsPerSample = 8;
322
            this.fact.chunkId = "fact";
323
            this.fact.chunkSize = 4;
324
            this.fact.dwSampleLength = samples.length;
325
        }
326
        // WAVE_FORMAT_EXTENSIBLE
327
        if (this.fmt.audioFormat == 65534) {
328
            this.chunkSize = 36 + 24 + samples.length * numBytes;
329
            this.fmt.chunkSize = 40;
330
            this.fmt.bitsPerSample = ((parseInt(bitDepth, 10) - 1) | 7) + 1;
331
            this.fmt.cbSize = 22;
332
            this.fmt.validBitsPerSample = parseInt(bitDepth, 10);
333
            this.fmt.dwChannelMask = 0;
334
            // subformat 128-bit GUID as 4 32-bit values
335
            // only supports uncompressed integer PCM samples
336
            this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
337
        }
338
    }
339
340
    /**
341
     * Init a WaveFile object from a byte buffer.
342
     * @param {!Uint8Array|!Array<number>} bytes The buffer.
343
     * @throws {Error} If container is not RIFF or RIFX.
344
     * @throws {Error} If no "fmt " chunk is found.
345
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
346
     * @throws {Error} If no "data" chunk is found.
347
     */
348
    fromBuffer(bytes) {
349
        this.readRIFFChunk_(bytes);
350
        let bigEndian = this.container == "RIFX";
351
        let chunk = riffChunks_.read(bytes, bigEndian);
352
        this.readFmtChunk_(chunk["subChunks"]);
353
        this.readFactChunk_(chunk["subChunks"]);
354
        this.readBextChunk_(chunk["subChunks"]);
355
        this.readCueChunk_(chunk["subChunks"]);
356
        this.readDataChunk_(
357
            chunk["subChunks"], {"be": bigEndian, "single": true});
358
        if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
359
            this.bitDepth = "32f";
360
        } else if (this.fmt.audioFormat == 6) {
361
            this.bitDepth = "8a";
362
        } else if (this.fmt.audioFormat == 7) {
363
            this.bitDepth = "8m";
364
        } else {
365
            this.bitDepth = this.fmt.bitsPerSample.toString();
366
        }
367
    }
368
369
    /**
370
     * Return a byte buffer representig the WaveFile object as a wav file.
371
     * The return value of this method can be written straight to disk.
372
     * @return {!Uint8Array} A .wav file.
373
     * @throws {Error} If any property of the object appears invalid.
374
     */
375
    toBuffer() {
376
        this.checkWriteInput_();
377
        this.assureInterleaved_();
378
        return this.createWaveFile_();
379
    }
380
381
    /**
382
     * Turn the file to RIFF.
383
     */
384
    toRIFF() {
385
        this.container = "RIFF";
386
        this.LEorBE_();
387
    }
388
389
    /**
390
     * Turn the file to RIFX.
391
     */
392
    toRIFX() {
393
        this.container = "RIFX";
394
        this.LEorBE_();
395
    }
396
397
    /**
398
     * Change the bit depth of the samples.
399
     * @param {!string} bitDepth The new bit depth of the samples.
400
     *      One of "8" ... "32" (integers), "32f" or "64" (floats)
401
     * @param {!boolean} changeResolution A boolean indicating if the
402
     *      resolution of samples should be actually changed or not.
403
     * @throws {Error} If the bit depth is not valid.
404
     */
405
    toBitDepth(bitDepth, changeResolution=true) {
406
        let toBitDepth = bitDepth;
407
        let thisBitDepth = this.bitDepth;
408
        if (!changeResolution) {
409
            toBitDepth = this.realBitDepth_(bitDepth);
410
            thisBitDepth = this.realBitDepth_(this.bitDepth);
411
        }
412
        this.assureInterleaved_();
413
        bitDepth_.toBitDepth(this.data.samples, thisBitDepth, toBitDepth);
414
        this.fromScratch(
415
            this.fmt.numChannels,
416
            this.fmt.sampleRate,
417
            bitDepth,
418
            this.data.samples,
419
            {"container": this.container}
420
        );
421
    }
422
423
    /**
424
     * Interleave multi-channel samples.
425
     */
426
    interleave() {
427
        if (!this.isInterleaved) {
428
            let finalSamples = [];
429
            let numChannels = this.data.samples[0].length;
430
            for (let i = 0; i < numChannels; i++) {
431
                for (let j = 0; j < this.data.samples.length; j++) {
432
                    finalSamples.push(this.data.samples[j][i]);
433
                }
434
            }
435
            this.data.samples = finalSamples;
436
            this.isInterleaved = true;    
437
        }
438
    }
439
440
    /**
441
     * De-interleave samples into multiple channels.
442
     */
443
    deInterleave() {
444
        if (this.isInterleaved) {
445
            let finalSamples = [];
446
            let i;
447
            for (i = 0; i < this.fmt.numChannels; i++) {
448
                finalSamples[i] = [];
449
            }
450
            i = 0;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
451
            let j;
452
            while (i < this.data.samples.length) {
453
                for (j = 0; j < this.fmt.numChannels; j++) {
454
                    finalSamples[j].push(this.data.samples[i+j]);
455
                }
456
                i += j;
457
            }
458
            this.data.samples = finalSamples;
459
            this.isInterleaved = false;
460
        }
461
    }
462
463
    /**
464
     * Encode a 16-bit wave file as 4-bit IMA ADPCM.
465
     * @throws {Error} If sample rate is not 8000.
466
     * @throws {Error} If number of channels is not 1.
467
     */
468
    toIMAADPCM() {
469
        if (this.fmt.sampleRate != 8000) {
470
            throw new Error(
471
                "Only 8000 Hz files can be compressed as IMA-ADPCM.");
472
        } else if(this.fmt.numChannels != 1) {
0 ignored issues
show
Best Practice introduced by
Comparing this.fmt.numChannels to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
473
            throw new Error(
474
                "Only mono files can be compressed as IMA-ADPCM.");
475
        } else {
476
            this.assure16Bit_();
477
            this.fromScratch(
478
                this.fmt.numChannels,
479
                this.fmt.sampleRate,
480
                "4",
481
                imaadpcm_.encode(this.data.samples),
482
                {"container": this.container}
483
            );
484
        }
485
    }
486
487
    /**
488
     * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
489
     */
490
    fromIMAADPCM() {
491
        this.fromScratch(
492
            this.fmt.numChannels,
493
            this.fmt.sampleRate,
494
            "16",
495
            imaadpcm_.decode(this.data.samples, this.fmt.blockAlign),
496
            {"container": this.container}
497
        );
498
    }
499
500
    /**
501
     * Encode 16-bit wave file as 8-bit A-Law.
502
     */
503
    toALaw() {
504
        this.assure16Bit_();
505
        this.assureInterleaved_();
506
        this.fromScratch(
507
            this.fmt.numChannels,
508
            this.fmt.sampleRate,
509
            "8a",
510
            alawmulaw_.alaw.encode(this.data.samples),
511
            {"container": this.container}
512
        );
513
    }
514
515
    /**
516
     * Decode a 8-bit A-Law wave file into a 16-bit wave file.
517
     */
518
    fromALaw() {
519
        this.fromScratch(
520
            this.fmt.numChannels,
521
            this.fmt.sampleRate,
522
            "16",
523
            alawmulaw_.alaw.decode(this.data.samples),
524
            {"container": this.container}
525
        );
526
    }
527
528
    /**
529
     * Encode 16-bit wave file as 8-bit mu-Law.
530
     */
531
    toMuLaw() {
532
        this.assure16Bit_();
533
        this.assureInterleaved_();
534
        this.fromScratch(
535
            this.fmt.numChannels,
536
            this.fmt.sampleRate,
537
            "8m",
538
            alawmulaw_.mulaw.encode(this.data.samples),
539
            {"container": this.container}
540
        );
541
    }
542
543
    /**
544
     * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
545
     */
546
    fromMuLaw() {
547
        this.fromScratch(
548
            this.fmt.numChannels,
549
            this.fmt.sampleRate,
550
            "16",
551
            alawmulaw_.mulaw.decode(this.data.samples),
552
            {"container": this.container}
553
        );
554
    }
555
556
    /**
557
     * Return the closest greater number of bits for a number of bits that
558
     * do not fill a full sequence of bytes.
559
     * @param {!string} bitDepth The bit depth.
560
     * @return {!string}
561
     */
562
    realBitDepth_(bitDepth) {
563
        if (bitDepth != "32f") {
564
            bitDepth = (((parseInt(bitDepth, 10) - 1) | 7) + 1).toString();
565
        }
566
        return bitDepth;
567
    }
568
569
    /**
570
     * Validate the input for wav writing.
571
     * @throws {Error} If any property of the object appears invalid.
572
     * @private
573
     */
574
    checkWriteInput_() {
575
        this.validateBitDepth_();
576
        this.validateNumChannels_();
577
        this.validateSampleRate_();
578
    }
579
580
    /**
581
     * Validate the bit depth.
582
     * @return {!boolean} True is the bit depth is valid.
583
     * @throws {Error} If bit depth is invalid.
584
     * @private
585
     */
586
    validateBitDepth_() {
587
        if (!this.audioFormats_[this.bitDepth]) {
588
            if (parseInt(this.bitDepth, 10) > 8 &&
589
                    parseInt(this.bitDepth, 10) < 54) {
590
                return true;
591
            }
592
            throw new Error("Invalid bit depth.");
593
        }
594
        return true;
595
    }
596
597
    /**
598
     * Validate the number of channels.
599
     * @return {!boolean} True is the number of channels is valid.
600
     * @throws {Error} If the number of channels is invalid.
601
     * @private
602
     */
603
    validateNumChannels_() {
604
        let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
605
        if (this.fmt.numChannels < 1 || blockAlign > 65535) {
606
            throw new Error("Invalid number of channels.");
607
        }
608
        return true;
609
    }
610
611
    /**
612
     * Validate the sample rate value.
613
     * @return {!boolean} True is the sample rate is valid.
614
     * @throws {Error} If the sample rate is invalid.
615
     * @private
616
     */
617
    validateSampleRate_() {
618
        let byteRate = this.fmt.numChannels *
619
            (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
620
        if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
621
            throw new Error("Invalid sample rate.");
622
        }
623
        return true;
624
    }
625
626
    /**
627
     * Reset the attributes related to the "fact" chunk.
628
     * @private
629
     */
630
    clearHeader_() {
631
        this.fmt.cbSize = 0;
632
        this.fmt.validBitsPerSample = 0;
633
        this.fact.chunkId = "";
634
        this.fact.chunkSize = 0;
635
        this.fact.dwSampleLength = 0;
636
    }
637
638
    /**
639
     * Make the file 16-bit if it is not.
640
     * @private
641
     */
642
    assure16Bit_() {
643
        this.assureUncompressed_();
644
        if (this.bitDepth != "16") {
645
            this.toBitDepth("16");
646
        }
647
    }
648
649
    /**
650
     * Uncompress the samples in case of a compressed file.
651
     * @private
652
     */
653
    assureUncompressed_() {
654
        if (this.bitDepth == "8a") {
655
            this.fromALaw();
656
        } else if(this.bitDepth == "8m") {
657
            this.fromMuLaw();
658
        } else if (this.bitDepth == "4") {
659
            this.fromIMAADPCM();
660
        }
661
    }
662
663
    /**
664
     * Interleave the samples in case they are de-Interleaved.
665
     * @private
666
     */
667
    assureInterleaved_() {
668
        if (!this.isInterleaved) {
669
            this.interleave();
670
        }
671
    }
672
673
    /**
674
     * Set up to work wih big-endian or little-endian files.
675
     * The types used are changed to LE or BE. If the
676
     * the file is big-endian (RIFX), true is returned.
677
     * @return {boolean} True if the file is RIFX.
678
     * @private
679
     */
680
    LEorBE_() {
681
        let bigEndian = this.container == "RIFX";
682
        uInt8_.be = bigEndian;
683
        uInt16_.be = bigEndian;
684
        uInt32_.be = bigEndian;
685
        return bigEndian;
686
    }
687
688
    /**
689
     * Find a chunk by its FourCC in a array of RIFF chunks.
690
     * @param {!Object<string, Object>} chunks The wav file chunks.
691
     * @param {!string} fourCC The chunk fourCC.
692
     * @return {Object|null}
693
     * @private
694
     */
695
    findChunk_(chunks, fourCC) {
696
        for (let i = 0; i<chunks.length; i++) {
697
            if (chunks[i]["chunkId"] == fourCC) {
698
                return chunks[i];
699
            }
700
        }
701
        return null;
702
    }
703
704
    /**
705
     * Read the RIFF chunk a wave file.
706
     * @param {!Uint8Array|!Array<number>} bytes A wav buffer.
707
     * @throws {Error} If no "RIFF" chunk is found.
708
     * @private
709
     */
710
    readRIFFChunk_(bytes) {
711
        this.container = byteData_.unpackArray(
712
            bytes.slice(0, 4), byteData_.chr);
713
        if (this.container != "RIFF" && this.container != "RIFX") {
714
            throw Error("Not a supported format.");
715
        }
716
        this.LEorBE_();
717
        this.chunkSize = byteData_.unpack(bytes.slice(4, 8), uInt32_);
718
        this.format = byteData_.unpackArray(
719
            bytes.slice(8, 12), byteData_.chr);
720
        if (this.format != "WAVE") {
721
            throw Error("Could not find the 'WAVE' format identifier");
722
        }
723
    }
724
725
    /**
726
     * Read the "fmt " chunk of a wave file.
727
     * @param {!Object<string, *>} chunks The wav file chunks.
728
     * @throws {Error} If no "fmt " chunk is found.
729
     * @private
730
     */
731
    readFmtChunk_(chunks) {
732
        let chunk = this.findChunk_(chunks, "fmt ");
733
        if (chunk) {
734
            this.fmt.chunkId = "fmt ";
735
            this.fmt.chunkSize = chunk["chunkSize"];
736
            this.fmt.audioFormat = byteData_.unpack(
737
                chunk["chunkData"].slice(0, 2), uInt16_);
738
            this.fmt.numChannels = byteData_.unpack(
739
                chunk["chunkData"].slice(2, 4), uInt16_);
740
            this.fmt.sampleRate = byteData_.unpack(
741
                chunk["chunkData"].slice(4, 8), uInt32_);
742
            this.fmt.byteRate = byteData_.unpack(
743
                chunk["chunkData"].slice(8, 12), uInt32_);
744
            this.fmt.blockAlign = byteData_.unpack(
745
                chunk["chunkData"].slice(12, 14), uInt16_);
746
            this.fmt.bitsPerSample = byteData_.unpack(
747
                    chunk["chunkData"].slice(14, 16), uInt16_);
748
            this.readFmtExtension_(chunk);
749
        } else {
750
            throw Error("Could not find the 'fmt ' chunk");
751
        }
752
    }
753
754
    /**
755
     * Read the "fmt " chunk extension.
756
     * @param {!Object<string, *>} chunk The "fmt " chunk.
757
     * @private
758
     */
759
    readFmtExtension_(chunk) {
760
        let chunkData = chunk["chunkData"];
761
        if (this.fmt.chunkSize > 16) {
762
            this.fmt.cbSize = byteData_.unpack(
763
                chunkData.slice(16, 18), uInt16_);
764
            if (this.fmt.chunkSize > 18) {
765
                this.fmt.validBitsPerSample = byteData_.unpack(
766
                    chunkData.slice(18, 20), uInt16_);
767
                if (this.fmt.chunkSize > 20) {
768
                    this.fmt.dwChannelMask = byteData_.unpack(
769
                        chunkData.slice(20, 24), uInt32_);
770
                    this.fmt.subformat = [
771
                        byteData_.unpack(
772
                            chunkData.slice(24, 28), uInt32_),
773
                        byteData_.unpack(
774
                            chunkData.slice(28, 32), uInt32_),
775
                        byteData_.unpack(
776
                            chunkData.slice(32, 36), uInt32_),
777
                        byteData_.unpack(
778
                            chunkData.slice(36, 40), uInt32_)];
779
                }
780
            }
781
        }
782
    }
783
784
    /**
785
     * Read the "fact" chunk of a wav file.
786
     * @param {!Object<string, *>} chunks The wav file chunks.
787
     * @throws {Error} If no "fact" chunk is found.
788
     * @private
789
     */
790
    readFactChunk_(chunks) {
791
        let chunk = this.findChunk_(chunks, "fact");
792
        if (chunk) {
793
            this.fact.chunkId = "fact";
794
            this.fact.chunkSize = chunk["chunkSize"];
795
            this.fact.dwSampleLength = byteData_.unpack(
796
                chunk["chunkData"].slice(0, 4), uInt32_);
797
        } else if (this.enforceFact_) {
798
            throw Error("Could not find the 'fact' chunk");
799
        }
800
    }
801
802
    /**
803
     * Read the "cue " chunk of a wave file.
804
     * @param {!Object<string, *>} chunks The RIFF file chunks.
805
     * @private
806
     */
807
    readCueChunk_(chunks) {
808
        let chunk = this.findChunk_(chunks, "cue ");
809
        if (chunk) {
810
            let chunkData = chunk["chunkData"];
811
            this.cue.chunkId = "cue ";
812
            this.cue.chunkSize = chunk["chunkSize"];
813
            this.cue.dwCuePoints = byteData_.unpack(
814
                chunkData.slice(0, 4), uInt32_);
815
            for (let i=0; i<this.cue.dwCuePoints; i++) {
816
                let offset = 4 + (i * 24);
817
                this.cue.points.push(
818
                    {
819
                        "dwName": byteData_.unpack(
820
                            chunkData.slice(offset, offset + 4), uInt32_),
821
                        "dwPosition": byteData_.unpack(
822
                            chunkData.slice(offset + 4, offset + 8), uInt32_),
823
                        "fccChunk": byteData_.unpack(
824
                            chunkData.slice(offset + 8, offset + 12), byteData_.fourCC),
825
                        "dwChunkStart": byteData_.unpack(
826
                            chunkData.slice(offset + 12, offset + 16), uInt32_),
827
                        "dwBlockStart": byteData_.unpack(
828
                            chunkData.slice(offset + 16, offset + 20), uInt32_),
829
                        "dwSampleOffset": byteData_.unpack(
830
                            chunkData.slice(offset + 20, offset + 24), uInt32_),
831
                    });
832
            }
833
        }
834
    }
835
836
    /**
837
     * Read the "data" chunk of a wave file.
838
     * @param {!Object<string, *>} chunks The RIFF file chunks.
839
     * @param {!Object<string, *>} options Type options.
840
     * @throws {Error} If no "data" chunk is found.
841
     * @private
842
     */
843
    readDataChunk_(chunks, options) {
844
        let chunk = this.findChunk_(chunks, "data");
845
        if (chunk) {
846
            this.data.chunkId = "data";
847
            this.data.chunkSize = chunk["chunkSize"];
848
            this.samplesFromBytes_(chunk["chunkData"], options);
849
        } else {
850
            throw Error("Could not find the 'data' chunk");
851
        }
852
    }
853
854
    /**
855
     * Read the "bext" chunk of a wav file.
856
     * @param {!Object<string, *>} chunks The wav file chunks.
857
     * @private
858
     */
859
    readBextChunk_(chunks) {
860
        let chunk = this.findChunk_(chunks, "bext");
861
        if (chunk) {
862
            let chunkData = chunk["chunkData"];
863
            this.head_ = 0;
864
            this.bext =  {
865
                "chunkId": "bext",
866
                "chunkSize": chunkData.length,
867
                "description": this.readString_(chunkData, 256),
868
                "originator": this.readString_(chunkData, 32),
869
                "originatorReference": this.readString_(chunkData, 32),
870
                "originationDate": this.readString_(chunkData, 10),
871
                "originationTime": this.readString_(chunkData, 8),
872
                "timeReference": [
873
                        this.readFromChunk_(chunkData, uInt32_),
874
                        this.readFromChunk_(chunkData, uInt32_)], 
875
                "version": this.readFromChunk_(
876
                    chunkData, uInt16_),
877
                "UMID": this.readString_(chunkData, 64),
878
                "loudnessValue": this.readFromChunk_(
879
                    chunkData, uInt16_),
880
                "loudnessRange": this.readFromChunk_(
881
                    chunkData, uInt16_),
882
                "maxTruePeakLevel": this.readFromChunk_(
883
                    chunkData, uInt16_),
884
                "maxMomentaryLoudness": this.readFromChunk_(
885
                    chunkData, uInt16_),
886
                "maxShortTermLoudness": this.readFromChunk_(
887
                    chunkData, uInt16_),
888
                "reserved": this.readString_(chunkData, 180),
889
                "codingHistory": this.readString_(
890
                    chunkData, chunkData.length - 602),
891
            };
892
        }
893
    }
894
895
    /**
896
     * Read bytes as a string from a RIFF chunk.
897
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
898
     * @param {!number} maxSize the max size of the string.
899
     * @return {!string} The string.
900
     * @private
901
     */
902
    readString_(bytes, maxSize) {
903
        let str = "";
904
        for (let i=0; i<maxSize; i++) {
905
            str += byteData_.unpack([bytes[this.head_]], byteData_.chr);
906
            this.head_++;
907
        }
908
        return str;
909
    }
910
911
    /**
912
     * Read a number from a chunk.
913
     * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
914
     * @param {!Object<string, *>} bdType The byte-data corresponding type.
915
     * @return {!number} The number.
916
     * @private
917
     */
918
    readFromChunk_(bytes, bdType) {
919
        let size = bdType.bits / 8;
920
        let value = byteData_.unpack(
921
            bytes.slice(this.head_, this.head_ + size), bdType);
922
        this.head_ += size;
923
        return value;
924
    }
925
926
    /**
927
     * Write a variable size string as bytes.
928
     * If the string is smaller than the max size it 
929
     * is filled with 0s.
930
     * @param {!string} str The string to be written as bytes.
931
     * @param {!number} maxSize the max size of the string.
932
     * @return {!Array<number>} The bytes.
933
     * @private
934
     */
935
    writeString_(str, maxSize) {
936
        let bytes = byteData_.packArray(str, byteData_.chr);
937
        for (let i=bytes.length; i<maxSize; i++) {
938
            bytes.push(0);
939
        }
940
        return bytes;
941
    }
942
943
    /**
944
     * Turn the samples to bytes.
945
     * @param {!Object<string, *>} options Type options.
946
     * @return {!Array<number>} The bytes.
947
     * @private
948
     */
949
    samplesToBytes_(options) {
950
        options.bits = this.fmt.bitsPerSample == 4 ?
951
            8 : this.fmt.bitsPerSample;
952
        options.signed = options.bits == 8 ? false : true;
953
        options.float = (this.fmt.audioFormat == 3  ||
954
                this.fmt.bitsPerSample == 64) ? true : false;
955
        let bytes = byteData_.packArray(
956
            this.data.samples, new byteData_.Type(options));
957
        if (bytes.length % 2) {
958
            bytes.push(0);
959
        }
960
        return bytes;
961
    }
962
963
    /**
964
     * Turn bytes to samples and load them in the data.samples property.
965
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
966
     * @param {!Object<string, *>} options Type options.
967
     * @private
968
     */
969
    samplesFromBytes_(bytes, options) {
970
        options.bits = this.fmt.bitsPerSample == 4 ?
971
            8 : this.fmt.bitsPerSample;
972
        options.signed = options.bits == 8 ? false : true;
973
        options.float = (this.fmt.audioFormat == 3 || 
974
            this.fmt.bitsPerSample == 64) ? true : false;
975
        options.single = false;
976
        this.data.samples = byteData_.unpackArray(
977
            bytes, new byteData_.Type(options));
978
    }
979
980
    /**
981
     * Return the bytes of the "bext" chunk.
982
     * @return {!Array<number>} The "bext" chunk bytes.
983
     * @private
984
     */
985
    getBextBytes_() {
986
        if (this.bext.chunkId) {
987
            return [].concat(
988
                byteData_.packArray(this.bext.chunkId, byteData_.chr),
989
                byteData_.pack(this.bext.chunkSize, uInt32_),
990
                this.writeString_(this.bext.description, 256),
991
                this.writeString_(this.bext.originator, 32),
992
                this.writeString_(this.bext.originatorReference, 32),
993
                this.writeString_(this.bext.originationDate, 10),
994
                this.writeString_(this.bext.originationTime, 8),
995
                byteData_.pack(this.bext.timeReference[0], uInt32_),
996
                byteData_.pack(this.bext.timeReference[1], uInt32_),
997
                byteData_.pack(this.bext.version, uInt16_),
998
                this.writeString_(this.bext.UMID, 64),
999
                byteData_.pack(this.bext.loudnessValue, uInt16_),
1000
                byteData_.pack(this.bext.loudnessRange, uInt16_),
1001
                byteData_.pack(this.bext.maxTruePeakLevel, uInt16_),
1002
                byteData_.pack(this.bext.maxMomentaryLoudness, uInt16_),
1003
                byteData_.pack(this.bext.maxShortTermLoudness, uInt16_),
1004
                this.writeString_(this.bext.reserved, 180),
1005
                this.writeString_(
1006
                    this.bext.codingHistory,
1007
                    this.bext.chunkSize - 602));
1008
        }
1009
        return [];
1010
    }
1011
1012
    /**
1013
     * Return the bytes of the "cue " chunk.
1014
     * @return {!Array<number>} The "cue " chunk bytes.
1015
     * @private
1016
     */
1017
    getCueBytes_() {
1018
        if (this.cue.chunkId) {
1019
            return [].concat(
1020
                byteData_.packArray(this.cue.chunkId, byteData_.chr),
1021
                byteData_.pack(this.cue.chunkSize, uInt32_),
1022
                byteData_.pack(this.cue.dwCuePoints, uInt32_),
1023
                this.getCuePointsBytes_());
1024
        }
1025
        return [];
1026
    }
1027
1028
    /**
1029
     * Return the bytes of the "cue " points.
1030
     * @return {!Array<number>} The "cue " points as an array of bytes.
1031
     * @private
1032
     */
1033
    getCuePointsBytes_() {
1034
        let points = [];
1035
        for (let i=0; i<this.cue.dwCuePoints; i++) {
1036
            points = points.concat(
1037
                    byteData_.pack(this.cue.points[i].dwName, uInt32_),
1038
                    byteData_.pack(this.cue.points[i].dwPosition, uInt32_),
1039
                    byteData_.pack(this.cue.points[i].fccChunk, byteData_.fourCC),
1040
                    byteData_.pack(this.cue.points[i].dwChunkStart, uInt32_),
1041
                    byteData_.pack(this.cue.points[i].dwBlockStart, uInt32_),
1042
                    byteData_.pack(this.cue.points[i].dwSampleOffset, uInt32_)
1043
                );
1044
        }
1045
        return points;
1046
    }
1047
1048
    /**
1049
     * Return the bytes of the "fact" chunk.
1050
     * @return {!Array<number>} The "fact" chunk bytes.
1051
     * @private
1052
     */
1053
    getFactBytes_() {
1054
        if (this.fact.chunkId) {
1055
            return [].concat(
1056
                byteData_.packArray(this.fact.chunkId, byteData_.chr),
1057
                byteData_.pack(this.fact.chunkSize, uInt32_),
1058
                byteData_.pack(this.fact.dwSampleLength, uInt32_));
1059
        }
1060
        return [];
1061
    }
1062
1063
    /**
1064
     * Return the bytes of the "fmt " chunk.
1065
     * @return {!Array<number>} The "fmt" chunk bytes.
1066
     * @private
1067
     * @throws {Error} if no "fmt " chunk is present.
1068
     */
1069
    getFmtBytes_() {
1070
        if (this.fmt.chunkId) {
1071
            return [].concat(
1072
                byteData_.packArray(this.fmt.chunkId, byteData_.chr),
1073
                byteData_.pack(this.fmt.chunkSize, uInt32_),
1074
                byteData_.pack(this.fmt.audioFormat, uInt16_),
1075
                byteData_.pack(this.fmt.numChannels, uInt16_),
1076
                byteData_.pack(this.fmt.sampleRate, uInt32_),
1077
                byteData_.pack(this.fmt.byteRate, uInt32_),
1078
                byteData_.pack(this.fmt.blockAlign, uInt16_),
1079
                byteData_.pack(this.fmt.bitsPerSample, uInt16_),
1080
                this.getFmtExtensionBytes_()
1081
            );
1082
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1083
            throw Error("Could not find the 'fmt ' chunk");
1084
        }
1085
    }
1086
1087
    /**
1088
     * Return the bytes of the fmt extension fields.
1089
     * @return {!Array<number>} The fmt extension bytes.
1090
     * @private
1091
     */
1092
    getFmtExtensionBytes_() {
1093
        let extension = [];
1094
        if (this.fmt.chunkSize > 16) {
1095
            extension = extension.concat(
1096
                byteData_.pack(this.fmt.cbSize, uInt16_));
1097
        }
1098
        if (this.fmt.chunkSize > 18) {
1099
            extension = extension.concat(
1100
                byteData_.pack(this.fmt.validBitsPerSample, uInt16_));
1101
        }
1102
        if (this.fmt.chunkSize > 20) {
1103
            extension = extension.concat(
1104
                byteData_.pack(this.fmt.dwChannelMask, uInt32_));
1105
        }
1106
        if (this.fmt.chunkSize > 24) {
1107
            extension = extension.concat(
1108
                byteData_.pack(this.fmt.subformat[0], uInt32_),
1109
                byteData_.pack(this.fmt.subformat[1], uInt32_),
1110
                byteData_.pack(this.fmt.subformat[2], uInt32_),
1111
                byteData_.pack(this.fmt.subformat[3], uInt32_));
1112
        }
1113
        return extension;
1114
    }
1115
    
1116
    /**
1117
     * Return a .wav file byte buffer with the data from the WaveFile object.
1118
     * The return value of this method can be written straight to disk.
1119
     * @return {!Uint8Array} The wav file bytes.
1120
     * @private
1121
     */
1122
    createWaveFile_() {
1123
        let options = {"be": this.LEorBE_()};
1124
        return new Uint8Array([].concat(
1125
            byteData_.packArray(this.container, byteData_.chr),
1126
            byteData_.pack(this.chunkSize, uInt32_),
1127
            byteData_.packArray(this.format, byteData_.chr),
1128
            this.getBextBytes_(),
1129
            this.getFmtBytes_(),
1130
            this.getFactBytes_(),
1131
            byteData_.packArray(this.data.chunkId, byteData_.chr),
1132
            byteData_.pack(this.data.chunkSize, uInt32_),
1133
            this.samplesToBytes_(options),
1134
            this.getCueBytes_()));
1135
    }
1136
}
1137
1138
module.exports = WaveFile;
1139